using RJCP.IO.Ports;
using System;
using System.Linq;
using System.Threading;

namespace ZbUtil.IO.Ports
{
    public class SerialPortStreamHelper : ISerialPortDataObserver
    {
        public event Action<string, byte[]> DataReceived = (portName, data) => { };
        public event Action<string, string> ErrorReceived = (portName, serialError) => { };
        public event Action<string, string> PinChanged = (portName, serialPinChange) => { };

        public string LastErrorMessage { get; private set; }

        #region SerialPortStream Attributes

        public string PortName { get; private set; }
        public int BaudRate => _serialPortStream.BaudRate;
        public Parity Parity => _serialPortStream.Parity;
        public int DataBits => _serialPortStream.DataBits;
        public StopBits StopBits => _serialPortStream.StopBits;

        public bool IsOpen => _opened || _serialPortStream.IsOpen;

        public bool DtrEnable
        {
            set => _serialPortStream.DtrEnable = value;
            get => _serialPortStream.DtrEnable;
        }

        public bool RtsEnable
        {
            set => _serialPortStream.RtsEnable = value;
            get => _serialPortStream.RtsEnable;
        }

        #endregion

        //曾经打开过串口
        private bool _opened;

        /// <summary>
        /// 是否使用接收超时机制
        /// 默认为true
        /// 接收到数据后计时，计时期间收到数据，累加数据，重新开始计时。超时后返回接收到的数据。
        /// </summary>
        private readonly bool _enableTimeout;

        private readonly SerialPortStream _serialPortStream;
        private readonly SerialPortDataHelper _dataHelper;

        public SerialPortStreamHelper(bool enableTimeout = true,
            int timeout = 128, int bufferSize = 4096)
            : this()
        {
            _enableTimeout = enableTimeout;
            _dataHelper = new SerialPortDataHelper(this, timeout, bufferSize);
        }

        public SerialPortStreamHelper(int minBuffer = 0)
            : this()
        {
            _enableTimeout = true;
            _dataHelper = new SerialPortDataHelper(this, minBuffer);
        }

        private SerialPortStreamHelper()
        {
            _serialPortStream = new SerialPortStream()
            {
                DtrEnable = true,
                RtsEnable = true
            };
            _serialPortStream.DataReceived += OnDataReceived;
            _serialPortStream.ErrorReceived += OnErrorReceived;
            _serialPortStream.PinChanged += OnPinChanged;
        }

        public static string[] GetPortNames() => SerialPortStream.GetPortNames();

        public static string BytesToHexStr(byte[] bytes) =>
            string.Join(" ", bytes.Select(t => t.ToString("X2")));

        public bool Open(string portName, int baudRate = 115200, Parity parity = Parity.None, int dataBits = 8,
            StopBits stopBits = StopBits.One)
        {
            PortName = portName;

            try
            {
                _serialPortStream.PortName = portName;
                _serialPortStream.BaudRate = baudRate;
                _serialPortStream.Parity = parity;
                _serialPortStream.DataBits = dataBits;
                _serialPortStream.StopBits = stopBits;

                if (_serialPortStream.IsDisposed)
                {
                    LastErrorMessage = "SerialPortStream is disposed";
                    return false;
                }

                _serialPortStream.Open();
                _dataHelper.Start();
                _opened = true;
                return true;
            }
            catch (Exception e)
            {
                LastErrorMessage = e.Message;
                return false;
            }
        }

        public void Close()
        {
            if (_serialPortStream.IsOpen)
            {
                _dataHelper.Stop();
                _serialPortStream.Close();
            }
        }

        public void Dispose()
        {
            _dataHelper.Stop();
            _serialPortStream.Dispose();
        }

        public void ReceiveData(byte[] data)
        {
            //在线程池中处理数据
            ThreadPool.QueueUserWorkItem((state) => { DataReceived?.Invoke(PortName, (byte[]) state); }, data);
        }

        private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (_enableTimeout)
            {
                while (_serialPortStream.BytesToRead > 0)
                {
                    var bytesToRead = _serialPortStream.BytesToRead;
                    var buffer = new byte[bytesToRead];

                    var bytesRead = _serialPortStream.Read(buffer, 0, bytesToRead);
                    if (bytesRead != bytesToRead)
                    {
                        throw new Exception("Serial port receives exception!");
                    }

                    _dataHelper.AddMessage(buffer);
                }
            }
            else
            {
                var bytesToRead = _serialPortStream.BytesToRead;
                if (bytesToRead == 0)
                {
                    return;
                }

                var buffer = new byte[bytesToRead];
                var offset = 0;
                while (offset < bytesToRead)
                {
                    //读取数据到缓冲区
                    offset += _serialPortStream.Read(buffer, offset, bytesToRead - offset);
                }

                ReceiveData(buffer);
            }
        }

        private void OnErrorReceived(object sender, SerialErrorReceivedEventArgs e)
        {
            ErrorReceived?.Invoke(PortName, e.EventType.ToString());
        }

        private void OnPinChanged(object sender, SerialPinChangedEventArgs e)
        {
            PinChanged?.Invoke(PortName, e.EventType.ToString());
        }

        #region Write

        public void Write(byte[] buffer)
        {
            if (IsOpen)
            {
                _serialPortStream.Write(buffer, 0, buffer.Length);
            }
        }

        public void Write(byte[] buffer, int offset, int count)
        {
            if (IsOpen)
            {
                _serialPortStream.Write(buffer, offset, count);
            }
        }

        public void Write(string text)
        {
            if (IsOpen)
            {
                _serialPortStream.Write(text);
            }
        }

        public void WriteLine(string text)
        {
            if (IsOpen)
            {
                _serialPortStream.WriteLine(text);
            }
        }

        #endregion
    }
}
